package com.puzzlebazar.client.squaregrid.model;

import java.util.Arrays;
import java.util.Vector;

import com.puzzlebazar.client.squaregrid.view.CellHighlighter;
import com.puzzlebazar.client.util.Vec2i;

public class StateGridUtils {

	/**
	 * Make sure all the cells of the specified states are adjacent to one another (horizontally
	 * or vertically), so that no isolated region exists.
	 * 
	 * @param model The model to check
	 * @param valid An array with all the cells to check
	 * @param highlighter Object to use to highlight faulty cells, only one region will be highlighted (null for no highlighting)
	 * @return True if all the cells are adjacent, false otherwise
	 */
	public static boolean areCellsAdjacent( StateGrid model, boolean[][] valid, CellHighlighter highlighter ) {
		
		Vec2i gridSize = model.getGridSize(); 
		if( gridSize.x <= 0 || gridSize.y <= 0 ) return true;
		
		try {
			boolean visited[][] = new boolean[gridSize.x][gridSize.y];
			for( int i=0; i<visited.length; ++i)
				Arrays.fill( visited[i], false );
			
			Vec2i loc = findValid(model, valid);
			if( loc == null ) return true;
			
			Vector<Vec2i> stack = new Vector<Vec2i>();
			stack.add(loc);
			visited[loc.x][loc.y] = true;
			while( !stack.isEmpty() ) {
				loc = stack.remove( stack.size()-1 );
				loc.x -= 1;
				if( loc.x>=0 ) checkAndAdd(visited, valid, loc, stack);
				loc.x += 2;
				if( loc.x<visited.length ) checkAndAdd(visited, valid, loc, stack);
				loc.x -= 1;
				loc.y -= 1;
				if( loc.y>=0 ) checkAndAdd(visited, valid, loc, stack);
				loc.y += 2;
				if( loc.y<visited[0].length ) checkAndAdd(visited, valid, loc, stack);
			}
			
			for( int i=0; i<valid.length; ++i ) {
				for( int j=0; j<valid[i].length; ++j ) {
					if( valid[i][j] && !visited[i][j] ) {
						if( highlighter != null )
							highlightCells( highlighter, valid, visited );
						return false;
					}
				}
			}
			
		} catch( Exception exception ) {
			exception.printStackTrace();
			return false;
		}
		
		return true;
	}


	/**
	 * Specialized method for areCellsAdjacent
	 */
	private static void checkAndAdd(boolean[][] visited, boolean[][] valid, Vec2i loc,
			Vector<Vec2i> stack) {
		if( !visited[loc.x][loc.y] && valid[loc.x][loc.y] ) {
			stack.add( new Vec2i(loc) );
			visited[loc.x][loc.y] = true;
		}
	}

	/**
	 * Check if there exists at least two adjacent cells (horizontally or vertically) of the given states.
	 * 
	 * @param model The model to check
	 * @param valid An array with all the cells to check
	 * @param highlighter Object to use to highlight faulty cells, only one pair will be highlighted (null for no highlighting)
	 * @return True if there exists at least two adjacent cells, false otherwise
	 */
	public static boolean doAdjacentCellsExist( StateGrid model, boolean[][] valid, CellHighlighter highlighter ) {

		Vec2i gridSize = model.getGridSize(); 
		if( gridSize.x <= 0 || gridSize.y <= 0 ) return true;
		
		try {
			for( int i=0; i<valid.length-1; ++i ) {
				boolean prevValid = false;
				for( int j=0; j<valid[i].length; ++j ) {
					if( valid[i][j] ) {
						if( prevValid ) {
							if( highlighter != null )
								highlighter.highlightCells( new Vec2i(i,j-1), new Vec2i(i,j) );
							return true;
						}
						prevValid = true;
						if( valid[i+1][j] ) {
							if( highlighter != null )
								highlighter.highlightCells( new Vec2i(i,j), new Vec2i(i+1,j) );
							return true;
						}
					}
					else
						prevValid = false;
				}
			}
			
		} catch( Exception exception ) {
			exception.printStackTrace();
			return false;
		}		

		return false;

	}

	/**
	 * Create a boolean array where each location is true if the corresponding cell
	 * contains one of the desired cell
	 * 
	 * @param model The model to use
	 * @param desiredStates The set of all cells to look for
	 * @return A boolean array correctly filled
	 */
	public static boolean[][] findCells(StateGrid model, int[] desiredStates) {

		Vec2i gridSize = model.getGridSize(); 
		boolean[][] result = new boolean[gridSize.x][gridSize.y];
		Vec2i loc = new Vec2i();
		for( int i=0; i<result.length; ++i ) {
			loc.x = i;
			for( int j=0; j<result[i].length; ++j ) {
				loc.y = j;
				try {
					result[i][j] = stateIn( model.getState(loc), desiredStates );
				} catch (Exception exception) {
					result[i][j] = false;
					exception.printStackTrace();
				}
			}
		}

		return result;
	}
	
	/**
	 * Returns one valid cell, or null if no such cell exist.
	 * 
	 * @param model The model to use
	 * @param valid An array containing one at each location of a valid cell
	 * @return The location of a valid cell, or null if no such cell are found
	 * @throws Exception 
	 */
	public static Vec2i findValid(StateGrid model, boolean[][] valid) throws Exception {

		for( int i=0; i<valid.length; ++i ) {
			for( int j=0; j<valid[i].length; ++j ) {
				if( valid[i][j] ) return new Vec2i(i,j);
			}
		}

		return null;
	}


	/**
	 * Checks that the passed state is found in the list of states
	 * 
	 * @param state State to check
	 * @param states List of states
	 * @return True if state is in states
	 */
	private static boolean stateIn(int state, int[] states) {
		for( int i = 0; i < states.length; ++i ) {
			if( state == states[i] ) return true;
		}
		return false;
	}

	/**
	 * Highlight cells that were both valid and visited
	 * 
	 * @param highlighter Highlighter object to use
	 * @param valid Array of valid cells
	 * @param visited Array of visited cells
	 * @throws Exception 
	 */
	private static void highlightCells(CellHighlighter highlighter,
			boolean[][] valid, boolean[][] visited) throws Exception {
		
		Vec2i loc = new Vec2i();
		for( int i=0; i<valid.length; ++i) {
			loc.x = i;
			for( int j=0; j<valid[i].length; ++j) {
				loc.y = j;
				if( valid[i][j] && visited[i][j] ) {
					highlighter.highlightCell( loc );
				}
			}
		}
	}


}
